package BasicClient;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.HashMap;


/**
 * This class is responsible for implementing the Forbes protocol. It reads data coming
 * from the server, parses it and calls the appropriate method from the Client interface.
 * @author pokeefe
 *
 */
class IncomingRequestHandlerThread extends Thread{
	
	private Client client;
	private DataInputStream in;
	private int tableSize;
	
	public IncomingRequestHandlerThread(Socket sock, Client client){
		this.client = client;
		this.tableSize = -1;
		try {
			this.in = new DataInputStream(sock.getInputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void settingID(){
		int playerID = 0;
		try {
			playerID = in.readInt(); // current ID
			playerID = in.readInt(); // new ID
			client.assignPlayerID(playerID);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	/**
	 * This run method will read in a byte, this is a header in the forbes protocol. Then,
	 * depending on the header value call a method to handle the incoming request. 
	 */
	public void run(){
		boolean running = true;
		while(running){
			byte header = -1;
			
			try {
				header = in.readByte();
			} catch (IOException e) {
				e.printStackTrace();
				// check if the server has disconnected and shut down the client as appropriate
				// otherwise this will infinite loop the exception forever
			}
			
			switch(header){
			
				/**
				 * Section for ServerToPlayer handing
				 */
				case Constants.ServerToPlayerConstants.ASSIGN_PLAYER_ID_HEADER:
					settingID();
					break;
				case Constants.ServerToPlayerConstants.HOLE_CARDS_HEADER:
					dealHoleCards();
					break;
				case Constants.ServerToPlayerConstants.ACTION_REQUIRED_HEADER:
					actionRequired();
					break;
					
					
				/**
				 * Section for ServerBroadcast handing
				 */
				case Constants.ServerBroadcastConstants.NEW_GAME_HEADER:
					newGame();
					break;
				case Constants.ServerBroadcastConstants.NEW_HAND_HEADER:
					newHand();
					break;
				case Constants.ServerBroadcastConstants.END_HAND_HEADER:
					endHand();
					break;
				case Constants.ServerBroadcastConstants.SET_BLINDS_HEADER:
					setBlinds();
					break;
				case Constants.ServerBroadcastConstants.SET_ORDER_HEADER:
					setOrder();
					break;				
				case Constants.ServerBroadcastConstants.PLAYER_DISC_CONN_HEADER:
					playerDiscConn();
					break;
				case Constants.ServerBroadcastConstants.FLOP_HEADER:
					flop();
					break;
				case Constants.ServerBroadcastConstants.TURN_HEADER:
					turn();
					break;
				case Constants.ServerBroadcastConstants.RIVER_HEADER:
					river();
					break;
					
					
				/**
				 * Section for PlayerBroadCast handing
				 */
				case Constants.PlayerBroadcastConstants.ACTION_FOLD_HEADER:
					actionFold();
					break;
				case Constants.PlayerBroadcastConstants.ACTION_CALL_HEADER:
					actionCall();
					break;
				case Constants.PlayerBroadcastConstants.ACTION_RAISE_HEADER:
					actionRaise();
					break;
					
				// note: no request blinds here. This is because the server responds with a Set Blinds message
				// note: no request seat here.   This is because the server responds with a Set Order  message
					
				/**
				 * Invalid header section	
				 */
				default:
					break;
			}
		}
	}


	private void actionRaise() {
		try {
			int playerID = in.readInt();
			int raise = in.readInt();
			client.playerRaise(playerID, raise);
		} catch (IOException e) {
			e.printStackTrace();
		}		
	}

	private void actionCall() {
		try {
			int playerID = in.readInt();
			client.playerCall(playerID);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void actionFold() {
		try {
			int playerID = in.readInt();
			client.playerFold(playerID);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

	private void flop() {
		try {
			int card1 = in.readByte();
			int card2 = in.readByte();
			int card3 = in.readByte();
			
			String[] cards = new String[3];
			cards[0] = this.intToStringCard(card1);
			cards[1] = this.intToStringCard(card2);
			cards[2] = this.intToStringCard(card3);
			client.flop(cards);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	private void turn(){
		try {
			int card = in.readByte();			
			String cardS = this.intToStringCard(card);
			client.turn(cardS);			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void river(){
		try {
			int card = in.readByte();
			String cardS = this.intToStringCard(card);
			client.turn(cardS);	
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void playerDiscConn() {
		try {
			int status = in.readByte();			
			int playerID = in.readInt();
			
			Status s = null;
			if(status == 0x00000000){
				s = Status.Disconnected;
			}else if(status == 0xFFFFFFFF){
				s = Status.Connected;
			}
			
			client.playerDisconnectReconnect(s, playerID);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

	private void endHand() {
		try {
			int numberOfWinners = in.readByte();
			
			
			HashMap<Integer, Integer> winnerMap = new HashMap<Integer, Integer>();
			for(int i=0; i<numberOfWinners; i++){
				int playerID = in.readInt();
				int potSize = in.readInt();
				winnerMap.put(playerID, potSize);
			}
			client.handEnd(winnerMap);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

	private void newHand() {
		try {
			int buttonLocation = in.readByte();
			int handID = in.readInt();
			int tableID = in.readInt();
			client.newHand(buttonLocation, handID, tableID);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

	private void newGame() {
		try {
			tableSize = in.readByte();
			int numOfPlayers = in.readInt();
			int stackSize = in.readInt();
			client.newGame(tableSize, numOfPlayers, stackSize);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void setBlinds() {
		try {
			int smallBlind = in.readInt();
			int bigBlind = in.readInt();
			int ante = in.readInt();
			client.setBlinds(smallBlind, bigBlind, ante);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void setOrder() {
		if(tableSize == -1){
			//game has no been set up properly, this will cause errors!!!
		}
		try {
			short tableID = in.readShort();
			int[] playerIDArray = new int[tableSize];
			for(int i=0; i<tableSize; i++){
				playerIDArray[i] = in.readByte();				
			}
			client.setOrder(tableID, playerIDArray);
		} catch (IOException e) {
			e.printStackTrace();
		}		
	}

	private void actionRequired() {
		try {
			int playerID = in.readInt();
			int toCallAmount = in.readInt();
			client.actionRequired(toCallAmount);
		} catch (IOException e) {
			e.printStackTrace();
		}		
	}

	private void dealHoleCards() {
		try {
			int playerID = in.readInt(); //this is redundant? 
			byte card1 = in.readByte();
			byte card2 = in.readByte();
			
			String[] cards = new String[2];
			cards[0] = this.intToStringCard(card1);
			cards[1] = this.intToStringCard(card2);
			client.setHoleCards(cards);			
		} catch (IOException e) {
			e.printStackTrace();
		}		
	}	
	
	private String intToStringCard(int card){
		int suit = card & 0x0030;
		int value = card & 0x000F;
		
		String suitS = "";		
		switch (suit){
			case 0x0000:
				suitS = "S";
				break;
			case 0x0010:
				suitS = "C";
				break;
			case 0x0020:
				suitS = "D";
				break;
			case 0x0030:
				suitS = "H";
				break;
		}
		
		String valueS = "";
		switch (value){
			case 0x0001:
				valueS = "A";
				break;
			case 0x0002:
				valueS = "2";
				break;
			case 0x0003:
				valueS = "3";
				break;
			case 0x0004:
				valueS = "4";
				break;
			case 0x0005:
				valueS = "5";
				break;
			case 0x0006:
				valueS = "6";
				break;
			case 0x0007:
				valueS = "7";
				break;
			case 0x0008:
				valueS = "8";
				break;
			case 0x0009:
				valueS = "9";
				break;
			case 0x000A:
				valueS = "T";
				break;
			case 0x000B:
				valueS = "J";
				break;
			case 0x000C:
				valueS = "Q";
				break;
			case 0x000D:
				valueS = "K";
				break;
		}
		
		return suitS+valueS;		
	}		
}

